package logger

import (
	"context"
	"math/rand"
	"net"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"

	"github.com/segmentio/kafka-go"
)

type fields struct {
	port            string
	logkey          string
	logmsg          string
	throwable       string
	user            string
	level           string
	logthread       string
	logname         string
	envtype         string
	appid           string
	requestidHeader string
	ctx             *gin.Context
}

// Field 字段值
type Field interface {
	apply(*fields)
}

type fieldFunc func(*fields)

func (f fieldFunc) apply(o *fields) {
	f(o)
}

// WithPort 端口
func WithPort(port string) Field {
	return fieldFunc(func(f *fields) {
		f.port = port
	})
}

// WithLogKey 日志关键字
func WithLogKey(key string) Field {
	return fieldFunc(func(f *fields) {
		f.logkey = key
	})
}

// WithLogMsg 日志内容
func WithLogMsg(logmsg string) Field {
	return fieldFunc(func(f *fields) {
		f.logmsg = logmsg
	})
}

// WithThrowable 异常堆栈
func WithThrowable(throwable string) Field {
	return fieldFunc(func(f *fields) {
		f.throwable = throwable
	})
}

// WithLevel 日志级别
func WithLevel(level string) Field {
	return fieldFunc(func(f *fields) {
		f.level = level
	})
}

// WithLogthread 日志进程
func WithLogthread(logthread string) Field {
	return fieldFunc(func(f *fields) {
		f.logthread = logthread
	})
}

// WithLogname 日志名
func WithLogname(logname string) Field {
	return fieldFunc(func(f *fields) {
		f.logname = logname
	})
}

// WithEnvtype 环境
func WithEnvtype(envtype string) Field {
	return fieldFunc(func(f *fields) {
		f.envtype = envtype
	})
}

// WithAppid 应用
func WithAppid(appid string) Field {
	return fieldFunc(func(f *fields) {
		f.appid = appid
	})
}

// WithUser 用户 ID-Name
func WithUser(user string) Field {
	return fieldFunc(func(f *fields) {
		f.user = user
	})
}

// WithRequestidHeader 用户 ID-Name
func WithRequestidHeader(requestidHeader string) Field {
	return fieldFunc(func(f *fields) {
		f.requestidHeader = requestidHeader
	})
}

// WithCtx Gin上下文
func WithCtx(ctx *gin.Context) Field {
	return fieldFunc(func(f *fields) {
		f.ctx = ctx
	})
}

// LogInner 应用日志
func LogInner(conn *Connection, fds ...Field) error {
	f := fields{
		level:           "INFO",
		port:            "0",
		user:            "-",
		logname:         "service.ProducerService",
		requestidHeader: "logcontext-requestid",
	}
	for _, fd := range fds {
		fd.apply(&f)
	}
	url := ""
	requestID := uuid.New().String()
	if f.ctx != nil {
		url = f.ctx.Request.RequestURI
		reqID := f.ctx.GetHeader(f.requestidHeader)
		if reqID == "" {
			f.ctx.Header(f.requestidHeader, requestID)
		} else {
			requestID = reqID
		}
	}

	msgKeys := []string{"logtime", "logthread", "logname", "loglevel", "requestid", "userid", "envtype", "appid", "server", "serverport", "url", "cspanid", "logmsg", "throwable"}

	msgMap := map[string]string{
		"logtime":    time.Now().Format("2006-01-02 15:04:05,000"),
		"logthread":  f.logthread,
		"logname":    f.logname,
		"loglevel":   f.level,
		"requestid":  requestID,
		"userid":     f.user,
		"envtype":    f.envtype,
		"appid":      f.appid,
		"server":     getLocalIP(),
		"serverport": f.port,
		"url":        url,
		"cspanid":    getRandNumStr(9),
		"logmsg":     f.logmsg,
		"throwable":  f.throwable,
	}
	msg := ""
	for _, key := range msgKeys {
		msg += "[" + msgMap[key] + "]"
	}

	if msg != "" {

		err := conn.writer.WriteMessages(
			context.Background(),
			kafka.Message{Value: []byte(msg)},
		)

		if err != nil {
			log.Error().Err(err).Msg("kafka conn error, retry !")
			if err = conn.Reload(); err != nil {
				log.Error().Err(err).Msg("kafka conn retry fail !")
				return err
			}

			if err = conn.writer.WriteMessages(
				context.Background(),
				kafka.Message{Value: []byte(msg)},
			); err != nil {
				log.Error().Err(err).Msg("kafka write fail !")
				return err
			}
		}
	}
	return nil
}

// getLocalIP get one non-loopback ip from net interface
func getLocalIP() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}
	for _, address := range addrs {
		// check the address type and if it is not a loopback the display it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return ""
}

func getRandNumStr(length int) string {
	//9位随机字数字
	seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
	charset := "123456789"
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[seededRand.Intn(len(charset))]
	}
	return string(b)
}
